
/*
 *  ___PROJECTNAMEASIDENTIFIER___Template.mm
 *  template
 *
 *  Created by SIO2 Interactive on 8/22/08.
 *  Copyright 2008 SIO2 Interactive. All rights reserved.
 *
 */

//File modified by Goffredo Marocchi (this is not the original SIO2 provided file,
//sio2 and its creator ROm are not responsible for the content of this file as it is now

#include "___PROJECTNAMEASIDENTIFIER___Template.h"

#import "sio2.h"
#import "SysTools.h"
#import "___PROJECTNAMEASIDENTIFIER___AppDelegate.h" 

#import "TextureMatrix.h"
#import "EAGLView.h"

const GLfloat squareVertices[] = {
	-100.0f, -100.0f, 0.0f,
	100.0f,  -100.0f, 0.0f,
	-100.0f,  100.0f, 0.0f,
	100.0f,   100.0f, 0.0f
};
const GLubyte squareColors[] = {
	255, 255,   0, 255,
	0,   255, 255, 255,
	0,     0,   0,   0,
	255,   0, 255, 255,
};

SIO2GL* SIO2GLobj;

//extern id sio2GLView;
extern BOOL backprojectionFix;
extern BOOL projectorEnable;


unsigned long t_physics_prev, t_physics_current, t_gameclock;
double t_accumulator = 0.0;
double t_globalClock = 0.0;

// Movement Direction ( Forward/Back ).
char  MOV_DIR = 0;
// Rotation Z.
float ROTZ = 0.0f;
// Rotation Direction ( Left/Right );
char ROTZ_DIR = 0;
// The smooth Z rotation component.
float SMOOTH_Z = 0.0f;
// Smooth Z attenuation factor.
float SMOOTH_FACTOR = 0.75f;
// The camera speed.
float SPEED = 0.0f;
// Touch screen sensitivity.
float SENSITIVITY = 5.0f;
// Handle to our SIO2mist structure
SIO2mist* _SIO2mist = NULL;
// Remember the origin of the light before
// transformation.
vec3 origin[ SIO2_LAMP_MAX ];

SIO2object* shadowReceivers[MAX_SHADOW_RECEIVERS];
// Our physic & culling thread handle.
SIO2thread*_SIO2thread = NULL;
// Should we refresh the thread?
unsigned char refresh_thread = 1;

unsigned int shadowReceivingObjects = 0;

#define BULLET_DEBUG (0)
#if BULLET_DEBUG==1
/*********************/
#include "GLDebugDrawer.h"
GLDebugDrawer debugDrawer;
/*********************/
#endif

//////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////
//////// template.mm functions                            ////////
//////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////
unsigned char custom_parser( char *_root, char *_tok, char *_val )
{
	// Print the root value of the current struct as well as the
	// custom token and its value. In order to affect theses values
	// to existing variable I recommend you to use sio2StringScanf,
	// its mechanism is similar as sscanf but much faster. The 
	// supported type are:
	//
	// %s = char*
	// %f = float
	// %d = int
	// %c = unsigned char
	// %u = unsigned short
	//
	// For more information on how to use it please check the
	// any load function of the sio2 struct supported in the
	// file format like: sio2ObjectLoad, sio2CameraLoad etc...
	//
	
	if (!VALID_STR(_tok) || !VALID_STR(_val)) return 0;
	
	
	char value[ SIO2_MAX_CHAR ] = {""};
	
	printf("object_normal_____: %s root:%s token:%s value:%s\n", sio2->_SIO2object->name, _root, _tok, _val );
	
	if (0==sio2StringCmp(_tok, "receivePlanarShadow")) {
		//we cannot do the sscanf operation before because we do not know what _val should be...
		//(in this case we know it is a char*)
		sio2StringScanf(_val, "%s", value);
		if (0==sio2StringCmp(value, STR_TRUE) && (shadowReceivingObjects < MAX_SHADOW_RECEIVERS)) {
			printf("object[%d]: %s root:%s token:%s value:%s\n", shadowReceivingObjects, sio2->_SIO2object->name, _root, _tok, _val );
			shadowReceivers[shadowReceivingObjects] = sio2->_SIO2object;
			
			printf("\nshadowReceiver[%d] = %p\n", shadowReceivingObjects, shadowReceivers[shadowReceivingObjects]);
			shadowReceivingObjects++;
		}
	}
	if (0==sio2StringCmp(_tok, "castShadow")) {
		printf("object_normal2: %s root:%s token:%s value:%s\n", sio2->_SIO2object->name, _root, _tok, _val );
		sio2EnableState(&sio2->_SIO2object->flags, SIO2_OBJECT_SHADOW);
		sio2ObjectUpdateType(sio2->_SIO2object);
		//if(0 == sio2IsStateEnabled(sio2->_SIO2object->flags, SIO2_OBJECT_SHADOW)) printf("\n.....!!!!!!!!!!!!...\n");
	}
	
	
	return 1;
}

void appLoader (void) {
	static unsigned char loaded = 0;
	
	MARK;
	MARK;
	
	if(nil==SIO2GLobj) SIO2GLobj = [(___PROJECTNAMEASIDENTIFIER___AppDelegate*)[[UIApplication sharedApplication]delegate] getSIO2GL_object];
	
	if( loaded == 0 )
	{
		INIT_OBJ_ARR_NULL (shadowReceivers, MAX_SHADOW_RECEIVERS);
		unsigned int i = 0;
		loaded = 1;
		vec3 gravity;
		vec3* shadowLight;
		
		gravity.x =   0.0f;
		gravity.y =   0.0f;
		gravity.z = -9.8f;
		
		t_physics_prev = t_physics_current = t_gameclock = CACurrentMediaTime(); //time is stored in seconds
		
		sio2->_SIO2physic = sio2PhysicInit((char *)"earth" );
		sio2PhysicSetGravity( sio2->_SIO2physic, &gravity );
		sio2PhysicPlay( sio2->_SIO2physic );
		
		sio2ResourceCreateDictionary( sio2->_SIO2resource );
		sio2->_SIO2resource->_SIO2entry[ SIO2_OBJECT ].custom = custom_parser;
		
#if DEBUG==1
		NSLog (@"debug");
		sio2ResourceOpen( sio2->_SIO2resource,
						 "myBlend.sio2", 1 );	
#else
		NSLog (@"release");
		sio2ResourceOpen( sio2->_SIO2resource,
						 "myBlend.sio2", 1 );
#endif		
		GLint tu;
		glGetIntegerv(GL_MAX_TEXTURE_UNITS, &tu);
		NSLog(@"MAX # of texture units = %d", tu);
		
		
		i=0;
		while( i != sio2->_SIO2resource->gi.number_entry )
		{
			sio2ResourceExtract( sio2->_SIO2resource, NULL );
			++i;
		}
		sio2ResourceClose( sio2->_SIO2resource );
		
		sio2ResetState();
		sio2ResourceBindAllInstances(sio2->_SIO2resource);
		sio2ResourceBindAllImages( sio2->_SIO2resource );
		sio2ResourceBindAllMaterials( sio2->_SIO2resource );
		sio2ResourceBindAllMatrix( sio2->_SIO2resource );
		sio2ResourceBindAllPhysicObjects( sio2->_SIO2resource,
										 sio2->_SIO2physic );
		sio2ResourceGenId( sio2->_SIO2resource );
		
		[SIO2GLobj  loadSIO2Camera:"camera/Camera"]; //loads the camera and set the MV and PROJ matrices.
		
		// Similar as the aproach above, here we loop for the number of
		// lamp held inside our resource manager, get the handle and record
		// the origin position of our lamp before transformation.
		SIO2lamp *_SIO2lamp;
		i = 0;
		while( i != sio2->_SIO2resource->n_lamp )
		{
			_SIO2lamp = ( SIO2lamp * )sio2->_SIO2resource->_SIO2lamp[ i ];
			
			origin[ i ].x = _SIO2lamp->_SIO2transform->loc->x;
			origin[ i ].y = _SIO2lamp->_SIO2transform->loc->y;
			origin[ i ].z = _SIO2lamp->_SIO2transform->loc->z; 
			
			++i;
		}
		sio2->_SIO2window->_SIO2windowrender = appRender;
		
		shadowLight = _SIO2lamp->_SIO2transform->loc;
		
		PRINT_SVEC ("light dir is: ", *_SIO2lamp->_SIO2transform->dir);
		
		MATRIX mProjection;
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		
		[SIO2GL matPerspective:&mProjection.f[0] withFov:sio2->_SIO2camera->fov
								   aspectRatio:sio2->_SIO2window->scl->x / sio2->_SIO2window->scl->y 
										 zNear:sio2->_SIO2camera->cstart zFar:sio2->_SIO2camera->cend
									  portrait:NO];
		
		[SIO2GLobj setUpdateCameraProjectionMatrix:TRUE];
		[SIO2GLobj renderCameraProjectionMatrix:&mProjection];
		glLoadMatrixf(&mProjection.f[0]);
		
		//if you use inverted the near and far clipping planes mapping for the current window then you have to change the depth tests...
		if(INVERTED_ZRANGE)  {
			glDepthFunc(GL_GREATER);
			glDepthRangef(1,0);
			glClearDepthf(0); //and adapth the z buffer clear value too...
		}
		
		[SIO2GLobj setObjShadowReceiver:&shadowReceivers[0]];
		
		[[SIO2GLobj sceneProjector] setProjector:@"camera/Projector"];
		[[SIO2GLobj sceneProjector] uploadProjectorTex:@"spotlight.png"];
		[[SIO2GLobj sceneProjector] uploadAttenuationTex:@"Attenuation2.png"];
		
		[SysTools checkGLerrors];
		
		//_SIO2thread = sio2ThreadInit();
	}
	/*if( sio2StringTok( sio2->sys, "iPod" ) )
	 {
	 sio2ThreadCreate( _SIO2thread,
	 templateCheckConnectionThread,
	 ( void * )sio2->_SIO2resource,
	 SIO2_THREAD_PRIORITY_VERY_HIGH );
	 }
	 else
	 {
	 sio2ThreadCreate( _SIO2thread,
	 templateCheckConnectionThread,
	 ( void * )sio2->_SIO2resource,
	 SIO2_THREAD_PRIORITY_HIGH );
	 }
	 sio2ThreadPlay( _SIO2thread );*/
	
	//[[sio2GLView getDelegate] connectionTest];
	
	//glEnable(GL_POLYGON_OFFSET_FILL);
}

void templateRender2D( void )
{
	sio2WindowEnter2D( sio2->_SIO2window, 0.0f, 1.0f );
	{
		ALPHA_BLENDING_ON(SIO2_MATERIAL_COLOR);
		
		// Standard GL calls...
		glVertexPointer( 3, GL_FLOAT, 0, squareVertices );
		glEnableClientState( GL_VERTEX_ARRAY );
		
		glColorPointer( 4, GL_UNSIGNED_BYTE, 0, squareColors );
		glEnableClientState( GL_COLOR_ARRAY );
		
		glPushMatrix();
		{
			// Get access to the current window width & height,
			// binded to the SIO2 structure and translate to
			// the middle of the screen.
			glTranslatef( sio2->_SIO2window->scl->x * 0.5f,
						 sio2->_SIO2window->scl->y * 0.5f, 0.0f );
			glRotatef(-90.0f, 0.0f, 0.0f, 1.0f);
			glScalef(0.25f, 0.25f, 1.0f);
			
			glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);	
		}
		glPopMatrix();
		
		ALPHA_BLENDING_OFF();
		glDisableClientState(GL_COLOR_ARRAY);
		glDisableClientState(GL_VERTEX_ARRAY);
		
		
		glRotatef(-90.0f, 0.0f, 0.0f, 1.0f);
		glTranslatef(-1024.0f, 10.0f, 0.0f);
		//[[[UIApplication sharedApplication] delegate] renderSampleText];
		
	}
	// Leave the 2D mode.
	sio2WindowLeave2D();
}

void appRender( void )
{	
	const GLfloat light0Ambient[] = {0.08, 0.08, 0.08, 1.0};
	//// Basic rendering loop setup at the beginning of the current frame
	{
		glStencilMask(0xFF);
		DEPTH_WRITES_ON();
		if(INVERTED_ZRANGE)  {
			glDepthFunc(GL_GREATER);
			glDepthRangef(1,0);
			glClearDepthf(0); //and adapth the z buffer clear value too...
		}
		
		glLightfv(GL_LIGHT0, GL_AMBIENT, light0Ambient);	
		glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );
		
		//glEnable(GL_POLYGON_OFFSET_FILL);
		//PolygonOffset::reset();
	}
	////
	
	static MATRIX mView;
	[SIO2GL matLookAt:&mView eye:sio2->_SIO2camera->_SIO2transform->loc 
								 vDir:sio2->_SIO2camera->_SIO2transform->dir upVector:(vec3*)&upWorld];

	//this takes care of doing the portrait to landscape transition
	[SIO2GLobj renderCameraModelViewMatrix:&mView enableLandscape:TRUE];
	{
		sio2LampEnableLight();
		[SIO2GLobj renderIPO];
		[SIO2GLobj renderLights];	
		glEnable(GL_LIGHTING);
		glEnable(GL_LIGHT0);
		glLightfv(GL_LIGHT0, GL_AMBIENT, light0Ambient);
		
		BOOL projEnable = projectorEnable;
		//projEnable = NO;
		if(projEnable){
			[[SIO2GLobj sceneProjector] calculateProjector];
			[[SIO2GLobj sceneProjector] rotateZ:0.48f];
			[[SIO2GLobj sceneProjector] submitProjectorGL];
			//

			if(INVERTED_ZRANGE) glDepthFunc(GL_GEQUAL);
			else glDepthFunc(GL_LEQUAL);
			
			renderPlanarShadows();
			
			SIO2_SOFT_RESET();
		}
		
		SIO2camera* projector = ( SIO2camera * )sio2ResourceGet( sio2->_SIO2resource,
																SIO2_CAMERA,
																(char*)"camera/Projector");
		[SIO2GL drawNormal:projector->_SIO2transform->dir ofPoint:projector->_SIO2transform->loc];
		[SIO2GLobj drawCameraFrustum:projector aspectRatio:1.0f];
		
		SIO2_SOFT_RESET();
	}
	//this resets the portrait to landscape transition
	MODELVIEW_LANDSCAPE_OFF();
	
	//templateRender2D();
	[SIO2GLobj setUpdateCameraModelViewMatrix:TRUE];
	[SysTools checkGLerrors];
	
	appGameLoop();
}

void appGameLoop() {
	
	LOCAL_AR_POOL_ALLOC();
	
	static BOOL displayLinkInitialized = NO;
	static float logicTickMS;
	
	if (!displayLinkInitialized) {
		NSLog(@"\n ... displayLink duration = %f", [SIO2GLobj  getCADLDuration]);
		[[DeviceInfo sharedDeviceInfo_instance] setRefreshTime:[SIO2GLobj  getCADLDuration]];
		[SIO2GLobj  stopAnimation];
		[SIO2GLobj  startAnimation];
		displayLinkInitialized = YES;	
		logicTickMS = [[DeviceInfo sharedDeviceInfo_instance] logicTimeMS];
	}
	
	//RENDER completed, continuing the canonical gameLoop
	float deltaTimeMS = sio2->_SIO2window->d_time * 0.001f;
	
	//update the physics engine
	bulletUpdate();
	
	deltaTimeMS = (deltaTimeMS<60.0f)? deltaTimeMS : 60.0f; //capping delta time
	t_accumulator += deltaTimeMS; //we accumulate time in the t_accumulator global variable
	
	while (t_accumulator >= logicTickMS) {
		
		//doWork (item, time_t = t_globalClock,  dt = logicTickMS); //pseudo code
		
		t_accumulator -= logicTickMS;
		t_globalClock += logicTickMS;
		//here we can fire an action every t = logicTickMS
		//doWorkItem();
	}
	LOCAL_AR_POOL_DRAIN();
}

void appShutdown( void )
{	
	glDisable(GL_POLYGON_OFFSET_FILL);
	printf("\nSIO2: preparing shutdown...\n" );
	[SIO2GLobj stopAnimation];
	[SysTools checkGLerrors];
	
	sio2ResourceUnloadAll( sio2->_SIO2resource );
	sio2->_SIO2resource = sio2ResourceFree( sio2->_SIO2resource );
	sio2->_SIO2window = sio2WindowFree( sio2->_SIO2window );
	
	printf("\nSIO2: shutdown...\n" );
	sio2 = sio2Shutdown();
}

// The coordinate of the original touch on the screen.
vec2 start;
void appScreenTap( void *_ptr, unsigned char _state )
{
	// When we release the tap stop the
	// camera movements.
	if( _state == SIO2_WINDOW_TAP_UP )
	{ MOV_DIR = 0; }
	else
	{
		start.x = sio2->_SIO2window->touch[ 0 ]->x;
		start.y = sio2->_SIO2window->touch[ 0 ]->y;
	}
}


void appScreenTouchMove( void *_ptr )
{
	/*
	 Calculate our movement direction based
	 on the delta of our touchscreen initial
	 point and current point.
	 */
	
	vec2 d;
	d.y = sio2->_SIO2window->touch[ 0 ]->x - start.x;
	d.x = start.y - sio2->_SIO2window->touch[ 0 ]->y;
	
	if( d.x > 5.0f )
	{
		MOV_DIR = -1;
		SPEED = ( d.x * 0.025f );
	}
	else if( d.x < -5.0f )
	{
		MOV_DIR = 1;		
		SPEED = -( d.x * 0.025f );
	}
	
	
#ifdef USE_SIMULATOR
	
	// If we got a movement from the device
	// between theses 2 thresholds.
	if( d.y > 5.0f || d.y < -5.0f )
	{
		// Use the Y axis of the accelerometer
		// for camera Z rotation.
		float tmp = d.y * SENSITIVITY * 0.001f;
		
		if( d.y > 0.0 )
		{ ROTZ_DIR = -1; }
		else if( d.y < 0.0 )
		{ ROTZ_DIR = 1; }
		
		if( abs( tmp ) > abs( SMOOTH_Z ) )
		{ SMOOTH_Z = abs( tmp ) * SMOOTH_FACTOR; }
		
		ROTZ -= tmp;
	}
#endif
}


void appScreenAccelerometer( void *_ptr )
{
#ifndef USE_SIMULATOR
	
	// If we got a movement from the device
	// between theses 2 thresholds.
	if( sio2->_SIO2window->accel->y > 0.1f || sio2->_SIO2window->accel->y < -0.1f )
	{
		// Use the Y axis of the accelerometer
		// for camera Z rotation.
		float tmp = sio2->_SIO2window->accel->y * SENSITIVITY;
		
		if( sio2->_SIO2window->accel->y > 0.0 )
		{ ROTZ_DIR = -1; }
		else if( sio2->_SIO2window->accel->y < 0.0 )
		{ ROTZ_DIR = 1; }
		
		if( abs( tmp ) > abs( SMOOTH_Z ) )
		{ SMOOTH_Z = abs( tmp ) * SMOOTH_FACTOR; }
		
		ROTZ -= tmp;
	}
	
#endif
}

void bulletUpdate( void ) {
	
	// Just to show that we can use the physic render
	// function at different location and adjust the
	// sequence according to the type of structure that
	// we want to achieve.
	sio2PhysicRender( sio2->_SIO2physic,
					 sio2->_SIO2window->d_time, 4 );
	
}

void renderPlanarShadows () {
	//glClear(GL_DEPTH_BUFFER_BIT);
	for (int i = 0; i < 3; i++) {
		if (NULL == shadowReceivers[i] && (i > 2)) {
			CMLog(@"......!!!!!!!!!!!!!!........")
			break;
		}
		else {
			if (NULL == shadowReceivers[i]) assert(0);
		}
		DPRINTF("\n\nindex i = %d\n\n", i);
		
		[[SIO2GLobj shadowMatrix]initWithLight:sio2->_SIO2lamp];
		[[SIO2GLobj shadowMatrix] setUpShadowMatrix:shadowReceivers[i]->name];
		
		// turning off writing to the color buffer and depth buffer, we want to write to the stencil buffer only
		RGBA_WRITES_OFF();
		DEPTH_WRITES_OFF();
		STENCIL_ON();
		
		// write a unique stencil value to the stencil buffer everywhere we are about to draw
		glStencilFunc(GL_ALWAYS, UNIQUE_STENCIL, 0xFF);
		// this is to always pass a one to the stencil buffer where we draw (only if depth test passes too)
		glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
		
		//render shadow receivers
		[SIO2GLobj renderShadowReceiverObject:shadowReceivers[i]]; //shadows are blended into the scene, so rendered last
		//render optional skybox -> render opaque geometry -> render transparent/alpha blended geometry
		
		// turn depth writes back on
		DEPTH_WRITES_ON();
		
		// until stencil test is diabled, only write to areas where the
		// stencil buffer has a one. This is to draw the shadow only on
		// the floor.
		glStencilFunc(GL_EQUAL, UNIQUE_STENCIL, 0xFF);	
		
		//glColor4f(0.08f, 0.08f, 0.08f, 1.0f);
		[[SIO2GLobj shadowMatrix] renderPlanarShadows];
		
		glDisable(GL_LIGHT0);
		glStencilFunc(GL_EQUAL, (UNIQUE_STENCIL+1), 0xFF); 
		glStencilOp(GL_KEEP, GL_KEEP, GL_DECR);
		
		RGBA_WRITES_ON();
		
		//glEnable(GL_BLEND);
		glBlendFunc(GL_ONE, GL_SRC_COLOR);
		
		[SIO2GLobj renderShadowReceiverObject:shadowReceivers[i]];

		glDisable(GL_BLEND);
		
		STENCIL_OFF();
		glEnable(GL_LIGHT0);
	}
}

